package study.Day_4;

/**
 * @Classname: Day_four_1
 * @Description: 题目在Day_four
 * @Date: 2021/9/18
 * @Author: huwei
 */
public class Day_four_1 {
    /**
     * 二分法解答两个数组中位数
     *
     * @param nums1 第一个数组
     * @param nums2 第二个数组
     * @return
     */
    public double getCenterNum(int[] nums1, int[] nums2) {
        // 第一个数组长度
        int m = nums1.length;
        // 第二个数组长度
        int n = nums2.length;
        // 取中间数的下标(不论奇偶: (m+n+1)/2 = (m+n+2)/2 都成立)
        // 对于奇数的情况，直接找到最中间的数即可，偶数的话需要求最中间两个数的平均值。
        // 为了简化代码，不分情况讨论，我们使用一个小trick，我们分别找第 (m+n+1) / 2 个，和 (m+n+2) / 2 个，然后求其平均值即可，这对奇偶数均适用
        // 假如 m+n 为奇数的话，那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等，相当于两个相同的数字相加再除以2，还是其本身。
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }

    /**
     * @param nums1 第一个数组
     * @param i     nums1的起始位置
     * @param nums2 第二个数组
     * @param j     nums2的起始位置
     * @param k     中位数下标
     *              <p>
     *   这里我们需要定义一个函数来在两个有序数组中找到第K个元素，下面重点来看如何实现找到第K个元素。
     *   首先，为了避免产生新的数组从而增加时间复杂度，我们使用两个变量i和j分别来标记数组nums1和nums2的起始位置。
     *   然后来处理一些边界问题，比如当某一个数组的起始位置大于等于其数组长度时，说明其所有数字均已经被淘汰了，相当于一个空数组了，
     *   那么实际上就变成了在另一个数组中找数字，直接就可以找出来了。还有就是如果K=1的话，那么我们只要比较nums1和nums2的起始位置i和j上的数字就可以了。
     *   难点就在于一般的情况怎么处理？因为我们需要在两个有序数组中找到第K个元素，为了加快搜索的速度，我们要使用二分法，对K二分，
     *   意思是我们需要分别在nums1和nums2中查找第K/2个元素，注意这里由于两个数组的长度不定，所以有可能某个数组没有第K/2个数字，
     *   所以我们需要先检查一下，数组中到底存不存在第K/2个数字，如果存在就取出来，否则就赋值上一个整型最大值。如果某个数组没有第K/2个数字，
     *   那么我们就淘汰另一个数组的前K/2个数字即可。有没有可能两个数组都不存在第K/2个数字呢，这道题里是不可能的，因为我们的K不是任意给的，
     *   而是给的m+n的中间值，所以必定至少会有一个数组是存在第K/2个数字的。最后就是二分法的核心啦，比较这两个数组的第K/2小的数字midVal1和midVal2的大小，
     *   如果第一个数组的第K/2个数字小的话，那么说明我们要找的数字肯定不在nums1中的前K/2个数字，所以我们可以将其淘汰，将nums1的起始位置向后移动K/2个，
     *   并且此时的K也自减去K/2，调用递归。反之，我们淘汰nums2中的前K/2个数字，并将nums2的起始位置向后移动K/2个，并且此时的K也自减去K/2，调用递归即可。
     * @return
     */
    public int findKth(int[] nums1, int i, int[] nums2, int j, int k) {
        if (i >= nums1.length) {
            return nums2[j + k - 1];//nums1为空数组
        }
        if (j >= nums2.length) {
            return nums1[i + k - 1];//nums2为空数组
        }
        if (k == 1) {
            System.out.println(Math.min(nums1[i], nums2[j]));
            // 当k = 1时,表示找到中位数,无法再分.又因为是重小到大排列,所以取较小值
            return Math.min(nums1[i], nums2[j]);
        }
        // 计算两个数组的起始位置+数组长度的中间值下标是否小于数组长度,是的话就取值,否则表示超过数组下标,取整形的最大值
        int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
        int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;
        // 如果midval1 < midval2 表示值在前面一个数组,就再对前面的数组进行二分,否则就是在后面的数组,对后面数组进行二分
        // 起始位置i(或者j)是当前位置加上中位数的二分,中位数(k)也减去本身的二分
        if (midVal1 < midVal2) {
            return findKth(nums1, i + k / 2, nums2, j, k - k / 2);
        } else {
            return findKth(nums1, i, nums2, j + k / 2, k - k / 2);
        }
    }
}
